/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 * */

// AmzServer - Tototoy's server model.
// Author: tonyjobmails@gmail.com (tonyhack).
//
// Define class Thread.
//
// Thread is a simple wrapper of pthread.
//

#include <signal.h>

#include "mutex.h"
#include "condition_variable.h"
#include "thread.h"
#include "logging.h"

namespace amz {

Thread::Thread(std::string name, bool joinable)
       : name_(name), id_(0), state_(THREAD_STATE_CREATE), joinable_(joinable) {
}

Thread::~Thread() {
}
  
void Thread::Sleep(uint32 msec) {
  usleep(msec * 1000);
}

void Thread::Alive() {
  this->state_ = THREAD_STATE_ALIVE;
}

void Thread::Terminal() {
  this->state_ = THREAD_STATE_TERMINAL;
}
  
bool Thread::Start() {
  if(this->CheckAlive()) {
    LOG(ERROR) << "Start failed, thread is alive!";
    return false;
  }
  
  pthread_attr_t attr;
  pthread_attr_init(&attr);
  if(this->CheckJoinable() == false) {
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
  }
  
  // Create a thread and let it executes the method - "ThreadFunction".
  if(pthread_create(&this->id_, &attr, Thread::ThreadFunction, this) != 0) {
    LOG(ERROR) << "Start failed, pthread_create error!";
    return false;
  }
  pthread_attr_destroy(&attr);
  
  // Block it, until alive state.
  this->mutex_.Lock();
  if(this->CheckAlive() == false) {
    // Not alive, wait for it. The method "ThreadFuction" will change state
    // into alive in it's thread.
    this->condition_variable_.Wait(&this->mutex_);
  }
  this->mutex_.Unlock();

  return true;
}

void *Thread::ThreadFunction(void *argument) {
  Thread *thread = static_cast<Thread *>(argument);
  
  // Alive.
  thread->mutex_.Lock();
  thread->Alive();
  thread->condition_variable_.Signal();
  thread->mutex_.Unlock();
  
  sigset_t sig_mask;
  sigfillset(&sig_mask);
  pthread_sigmask(SIG_SETMASK, &sig_mask, NULL);

  // Update time.
  thread->time_.Update();

  // Callback.
  thread->Run();
  
  // Terminal.
  thread->mutex_.Lock();
  thread->Terminal();
  thread->condition_variable_.Broadcast();
  thread->mutex_.Unlock();

  return thread;
}

void Thread::Join() {
  if(this->id_ && this->CheckJoinable()) {
    this->mutex_.Lock();
    if(this->CheckAlive()) {
    this->condition_variable_.Wait(&this->mutex_);
  }
  this->mutex_.Unlock();
#ifdef _AMZ_DEBUG
  LOG(WARNING) << "join thread id=" << this->id_;
#endif
  pthread_join(this->id_, NULL);
  this->id_ = 0;
  }
}

}  // namespace amz

